package ci.web.router;

import io.netty.handler.codec.http.HttpResponseStatus;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;

import ci.web.HttpMethod;
import ci.web.annotaction.BodyParam;
import ci.web.annotaction.FileParam;
import ci.web.annotaction.LimitMethod;
import ci.web.annotaction.Param;
import ci.web.annotaction.PathParam;
import ci.web.annotaction.Router;
import ci.web.codec.FileItem;
import ci.web.core.CiContext;

import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;

/**
 * 路由-脚本处理器
 * @author zhh
 */
public class CiHandler implements CiCall{

    protected String path;
    protected boolean pathArg;
    protected int _hashcode;
    protected Executable method;
    protected LimitMethod limitMethod;
    
    public CiHandler(String pkg, Method method) {
        this.method = method;
        path = buildPath(method, pkg, className(), methodName());
        pathArg = isPathArg(method);
        _hashcode = pathArg?(path+"@").hashCode():path.hashCode();
        limitMethod = method.getAnnotation(LimitMethod.class);
    }
    protected CiHandler(){
    }
    
    protected String className(){
        return method.getDeclaringClass().getCanonicalName();
    }
    protected String methodName(){
        return method.getName();
    }
    
    @Override
    public int hashCode() {
        return _hashcode;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CiHandler){
            return obj==this;
        }
        return super.equals(obj);
    }
    
    @Override
    public String toString() {
        return String.format("{p:%s, j:%s.%s}", path, className(), methodName());
    }

    /**
     * 执行处理
     * @param ctx
     * @return
     * @throws Exception
     */
    @Override
    public boolean call(CiContext ctx, ClassLoader loader) throws Exception {
        if(isAlowMethod(ctx.method())==false){
            ctx.out().setStatus(HttpResponseStatus.FORBIDDEN);
        }else{
            Object[] args = castArgs(ctx, method, path);
            if (Modifier.isStatic(method.getModifiers())) {
                ((Method)method).invoke(null, args);
            } else {
                ((Method)method).invoke(method.getDeclaringClass().newInstance(), args);
            }
        }
        return true;
    }
    /**
     * 方法是否被允许
     * @param requestMethod
     * @return
     */
    protected boolean isAlowMethod(HttpMethod requestMethod) {
    	return limitMethod ==null || limitMethod.value()==requestMethod;
    }
    /**
     * 参数转换
     * @param ctx
     * @param method
     * @param basePath
     * @return
     */
    protected static Object[] castArgs(CiContext ctx, Executable method, String basePath) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Object[] args = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> clazz = parameterTypes[i];
            Annotation[] annotations = parameterAnnotations[i];
            Object value = null;
            //请求上下文做为参数
            if (clazz.isAssignableFrom(CiContext.class)) {
                value = ctx;
            }else if(clazz==File.class){
            	//有注解，则使用注解作为 文件name
    			if(annotations.length==1 && annotations[0] instanceof FileParam){
    				String name = ((FileParam) annotations[0]).value();
    				FileItem item = ctx.getFile(name);
                    value = item==null?null:item.getFile();
    			}else{
    				//默认File参数，无注解情况，选第一个文件-作为参数
                	FileItem item = ctx.files().size()>0 ? ctx.files().get(0):null;
                	value = item==null?null:item.getFile();
    			}
    		}else if(clazz==byte[].class && annotations.length==0){
    			value = ctx.body();
    		}else if (annotations.length == 1) {
                if (annotations[0] instanceof Param) {
                  //普通-请求参数
                    String name = ((Param) annotations[0]).value();
                    Object paramValue = ctx.params().get(name);
                    value = TypeUtils.cast(paramValue, clazz, ParserConfig.getGlobalInstance());
                } else if (annotations[0] instanceof BodyParam) {
                    //使用body做为参数，直接获取其byte数组
                    if(clazz==String.class){
                    	value = new String(ctx.body());
                    }else{
                    	value = ctx.body();
                    }
                } else if(annotations[0] instanceof PathParam){
                    //路径参数处理- 路径删除Class.Method然后做为参数, 这里支持两种
                    // 1.路径做参数 2.使用斜杠切分指定某一位做参数
                    String s = ctx.path().replace(basePath, "").substring(1);
                    if(clazz == String[].class){
                        value = s.split("/");
                    }else{
                        int idx = ((PathParam) annotations[0]).value();
                        if(idx<0){
                            value = s;
                        }else{
                            String[] sarr = s.split("/");
                            value = idx>=sarr.length ? null:sarr[idx];
                        }
                        value = TypeUtils.cast(value, clazz, ParserConfig.getGlobalInstance());
                    }
                }
            }
            args[i] = value;
        }
        return args;
    }
    
    /**
     * 构建路由路径
     * @param method
     * @param pkg
     * @param className
     * @param methodName
     * @return
     */
    protected static String buildPath(Executable method, String pkg, String className, String methodName){
        Router r = method.getDeclaringClass().getAnnotation(Router.class);
        if(r != null){
            className = r.value().toLowerCase();
        }else{
            className = className.toLowerCase().replace(pkg, "").replaceAll("\\.", "/");
        }
        r = method.getAnnotation(Router.class);
        if(r != null){
            methodName = r.value();
            if(methodName.endsWith("*")){//如果函数路由最后以*结尾，认为是参数Handler
                methodName = methodName.substring(0, methodName.length()-1);
            }
        }
        methodName = methodName.toLowerCase();
        if((className.isEmpty() && methodName.isEmpty()) || (className.equals("/") && methodName.equals("/"))){
            return "/";
        }
        String p = className;
        if(methodName.isEmpty() || className.isEmpty()){
            p = className+methodName;
        }else if(methodName.charAt(0)!='/' && className.charAt(className.length()-1)!='/'){
            p = className+'/'+methodName;
        }else{
            p = className+methodName;
        }
        return p.charAt(0)=='/' ? p:'/'+p;
    }
    /**
     * 路径做为参数的方法
     * @param method
     * @return
     */
    protected static boolean isPathArg(Executable method) {
        Router r = method.getAnnotation(Router.class);
        if(r != null && r.value().endsWith("*")){//如果函数路由最后以*结尾，认为是参数Handler
            return true;
        }
        Annotation[][] pas = method.getParameterAnnotations();
        Annotation[] as = null;
        for(int i=1;i<pas.length;i++){
            as = pas[i];
            if(as.length>0 && as[0] instanceof PathParam){
                return true;
            }
        }
        return false;
    }
    /**
     * 检查类是否适合ci路由
     * @param clazz
     * @return
     */
    public static boolean check(Class<?> clazz) {
        boolean canInstance = false;
        for(Constructor<?> constructor : clazz.getConstructors()){
            if(constructor.getParameterCount()==0){
                canInstance = true;
            }
        }
        return canInstance;
    }
    /**
     * 检查方法是否适合ci路由
     * @param method
     * @return
     */
    public static boolean check(Executable method) {
        if(method.getParameterCount()>0){
            Class<?>[] types = method.getParameterTypes();
            //按CI规定，第一个参数必须为请求上下文
            if(types[0]==CiContext.class){
                for(int i=1; i<types.length; i++){
                    if(!isWebType(types[i])){
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 检测支持的参数
     * @param clazz
     * @return
     */
    protected static boolean isWebType(Class<?> clazz) {
        return clazz.isPrimitive() || 
        clazz==Boolean.class ||
        clazz==Byte.class ||
        clazz==Short.class ||
        clazz==Integer.class ||
        clazz==Long.class ||
        clazz==Float.class ||
        clazz==Double.class ||
        clazz==Character.class ||
        clazz==String.class ||
        clazz==byte[].class ||
        clazz==File.class ||
        clazz==String[].class||
        java.util.Date.class.isAssignableFrom(clazz) ||
        Map.class.isAssignableFrom(clazz) ||
        Collection.class.isAssignableFrom(clazz)
        ;
    }


}
